#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <openssl/md5.h>
#include <stdlib.h> 
#include <stdarg.h>

#define CMD_PORT 7700    //UDP连接端口
#define DATA_PORT 6000    //第一路TCP连接端口，第二路TCP连接端口默认加1
#define N 64
#define DOWNLOAD_DIR "/home/hq/rev"  // 选择下载路径路径（没加检测，确保文件夹存在）

char Ture_Buf[N] = "device discovery"; // 匹配协议
char Reply_Buf[N] = "device matching";  //搜索回复
char full_path[256]; //下载的路径

// 随便定义的设备版本和MD5
char Ver[32] = "v1.0.1";
char Md[32] = "AABBCCDDEEFF";

char buf[N] = {};
char buffer[1024] = {}; // 第一路TCP连接接收
char buf_Io[1024] = {}; // 第二路TCP连接接收
char buf_Name[64] = {}; // 文件名（传输和下载的文件名相同）
char buf_Md5[64] = {};  // 传输文件的mds
uint64_t rev_size = 0; //  大小
int clifd_rev;  //第一路tcp
char md5_str[64] = {0}; //下载文件的mds

struct stat file_stat;

//获取md5的函数
void calculate_md5(const char* filepath) 
{
    unsigned char md5_result[MD5_DIGEST_LENGTH];
    int fd = open(filepath, O_RDONLY);
    
    if (fd == -1) 
    {
        perror("打开文件失败");
        return;
    }
    MD5_CTX ctx;
    MD5_Init(&ctx);

    unsigned char buf[1024 * 16];  // 16KB缓冲区
    ssize_t bytes_read;
    
    while ((bytes_read = read(fd, buf, sizeof(buf))) > 0) 
    {
        MD5_Update(&ctx, buf, bytes_read);
    }

    MD5_Final(md5_result, &ctx);
    close(fd);

    // 转换为可读字符串
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) 
    {
        sprintf(md5_str + i*2, "%02x", md5_result[i]);
    }
}

//系统封装，指令执行
int system_cmd(const char *format, ...)
{
    va_list arg;
    char cmd[256];
    int status;

    va_start(arg, format);
    vsnprintf(cmd, sizeof(cmd), format, arg);
    va_end(arg);

    status = system(cmd);
    if (-1 == status || !WIFEXITED(status) || 0 != WEXITSTATUS(status)) 
    {
        printf("[%s] 执行失败\n", cmd);
        return -1;
    }
    return 0;
}

//第二路TCP连接，升级线程
void* update_thread(void *arg)
{
    // 文件
    snprintf(full_path, sizeof(full_path), "%s/%s", DOWNLOAD_DIR, buf_Name);
    int fd = open(full_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);

    if (fd < 0)
    {
        perror("open err");
        return NULL;
    }

    // sock_update这一路连接是专门用来接收升级文件包的
    int sock_update = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_update < 0)
    {
        perror("socket err");
        close(fd);
        return NULL;
    }

    // 允许端口立即重用
    int opt = 1;
    setsockopt(sock_update, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in data_addr;
    data_addr.sin_family = AF_INET;
    data_addr.sin_port = htons(DATA_PORT + 1);
    data_addr.sin_addr.s_addr = INADDR_ANY;

    socklen_t addrlen = sizeof(data_addr);

    if (bind(sock_update, (struct sockaddr *)&data_addr, addrlen) < 0)
    {
        perror("bind err");
        close(fd);
        close(sock_update);
        return NULL;
    }

    if (listen(sock_update, 5) < 0)
    {
        perror("listen err");
        close(fd);
        close(sock_update);
        return NULL;
    }

    ssize_t len = -1;
    struct sockaddr_in soft_addr;

    int clifd = accept(sock_update, (struct sockaddr *)&soft_addr, &addrlen);
    if (clifd < 0)
    {
        perror("accept err");
        close(fd);
        close(sock_update);
        return NULL;
    }
    printf("第二路TCP连接成功\n");
    
    printf("client ip : %s  port:%d\n", inet_ntoa(soft_addr.sin_addr), ntohs(soft_addr.sin_port));

    cJSON *tree = cJSON_CreateObject();
    cJSON *tree2 = cJSON_CreateObject();
    while (1)
    {
        len = recv(clifd, buf_Io, sizeof(buf_Io), 0);

        if (write(fd, buf_Io, len) != len)
        {
            perror("write err");
            break;
           
        }
        if (fstat(fd, &file_stat) != 0) 
            perror("无法验证文件"); 
        if((uint64_t)file_stat.st_size==rev_size)
            break;    
    }
    //获得下载文件的md5
    calculate_md5(full_path);

    cJSON_AddItemToObject(tree, "type", cJSON_CreateNumber(2));
    cJSON *grass = cJSON_CreateObject();

    //校验并回复
    if(strcmp(md5_str,buf_Md5)==0)
    {
        cJSON_AddStringToObject(grass, "trans_success", "true");
    }else
    {
        cJSON_AddStringToObject(grass, "trans_success", "false");
    }
    cJSON_AddItemToObject(tree, "data", grass);
    char *p = cJSON_Print(tree);

    sleep(1);
    send(clifd_rev, p, strlen(p), 0);
    printf("升级包接受成功\n");
    free(p);
    cJSON_Delete(tree);

    //自升级（解压升级包）
    
    printf("开始升级......\n");
    printf("\n");
    sleep(1);
    char unzip_cmd[256];
    snprintf(unzip_cmd, sizeof(unzip_cmd), "unzip -o %s/%s -d %s", DOWNLOAD_DIR, buf_Name, DOWNLOAD_DIR);
    system_cmd(unzip_cmd);

    //升级回复
    cJSON_AddItemToObject(tree2, "type", cJSON_CreateNumber(3));
    cJSON *grass2 = cJSON_CreateObject();
    cJSON_AddStringToObject(grass2, "update_success", "true");
    cJSON_AddItemToObject(tree2, "data", grass2);
    char *p2 = cJSON_Print(tree2);
    send(clifd_rev, p2, strlen(p2), 0);

    printf("\n升级成功\n");
    free(p2);   
    cJSON_Delete(tree2);
    close(fd);
    close(clifd);
    close(sock_update);
 
    pthread_detach(pthread_self());
    pthread_exit(NULL);
    return NULL;
}


int main(int argc, char const *argv[])
{
    // 定义下载线程
    pthread_t updata;

    // UDP接受广播包
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }

    struct sockaddr_in dev_addr;
    dev_addr.sin_family = AF_INET;
    dev_addr.sin_port = htons(CMD_PORT);
    dev_addr.sin_addr.s_addr = INADDR_ANY;

    socklen_t addrlen = sizeof(dev_addr);

    if (bind(sockfd, (struct sockaddr *)&dev_addr, sizeof(dev_addr)) < 0)
    {
        perror("bind err");
        return -1;
    }

    ssize_t len = -1;
    struct sockaddr_in soft_addr;

    memset(buf, 0, N);
    recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&soft_addr, &addrlen);

    // 校验广播协议
    if (strcmp(buf, Ture_Buf) == 0)
    {
        printf("收到了自己的APP的搜索协议\n");
    }
    else
    {
        printf("其它的广播，不处理");
        return -1;
    }
    soft_addr.sin_port = htons(7700);

    // 已收到自家搜索,回复协议
    int bytes_sent = sendto(sockfd, Reply_Buf, strlen(Reply_Buf), 0, (struct sockaddr *)&soft_addr, addrlen);
    if (bytes_sent < 0)
    {
        perror("sendto failed");
        return -1;
    }
    printf("Reply sent to %s:%d\n", inet_ntoa(soft_addr.sin_addr), ntohs(soft_addr.sin_port));
    //.........................................................................................................
    //.........................................................................................................
    //.........................................................................................................
    //.........................................................................................................
    // 化身TCP服务器，建立第一路TCP长连接
    int tcpfd = socket(AF_INET, SOCK_STREAM, 0);
    if (tcpfd < 0)
    {
        perror("socket err");
        return -1;
    }

    // 填充结构体  （端口可复用，可更改）
    // struct sockaddr_in saddr;
    // saddr.sin_family = AF_INET;
    dev_addr.sin_port = htons(DATA_PORT);
    // saddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定信息
    int ret = bind(tcpfd, (struct sockaddr *)&dev_addr, addrlen);
    if (ret < 0)
    {
        perror("bind err");
        return -1;
    }
    // 监听
    if (listen(tcpfd, 5) < 0)
    {
        perror("listen err");
        return -1;
    }
    struct sockaddr_in tcp_addr;
    // 接受客户端连接
    clifd_rev = accept(tcpfd, (struct sockaddr *)&tcp_addr, &addrlen);
    if (clifd_rev < 0)
    {
        perror("accept err");
    }
    printf("数据发送端已经连入\n");

    while (1)
    {
        ret = recv(clifd_rev, buffer, sizeof(buffer), 0);
        if (ret == -1)
        {
            perror("recv err:");
            return -1;
        }
        else if (ret == 0) // 客户端退出设备端这也会退出
        {
            printf("client exit\n");
            break;
        }
        //..............................................................................................
        cJSON *root = cJSON_Parse(buffer);
        if (NULL == root)
        {
            printf("未知命令\n");
            send(clifd_rev, "请重新发送", 32, 0);
            continue;
        }
        //解析问询包
        cJSON *item = cJSON_GetObjectItem(root, "type");

        cJSON *data = cJSON_GetObjectItem(root, "data");
        cJSON *ver = cJSON_GetObjectItem(data, "ver");

        cJSON *file_name = cJSON_GetObjectItem(data, "file_name");
        snprintf(buf_Name, sizeof(buf_Name), "%s", file_name->valuestring);

        cJSON *file_len = cJSON_GetObjectItem(data, "file_len");
        rev_size = file_len->valueint;

        cJSON *md5 = cJSON_GetObjectItem(data, "md5");
        snprintf(buf_Md5, sizeof(buf_Md5), "%s", md5->valuestring);
        //..............................................................................................

        cJSON *tree = cJSON_CreateObject();
        // 判断是否升级
        switch (item->valueint)
        {
        case 1:

            cJSON_AddItemToObject(tree, "type", cJSON_CreateNumber(1));
            cJSON *grass = cJSON_CreateObject();

            //不升级回复
            if ((strcmp(Ver, ver->valuestring) == 0) && (strcmp(Md, md5->valuestring) == 0))
            {
                cJSON_AddStringToObject(grass, "update", "false");
                cJSON_AddItemToObject(tree, "data", grass);
                char *p = cJSON_Print(tree);
                send(clifd_rev, p, strlen(p), 0);
                free(p);
            }
            //升级回复，开辟升级线程
            else
            {
                cJSON_AddStringToObject(grass, "update", "true");
                cJSON_AddItemToObject(tree, "data", grass);
                char *p = cJSON_Print(tree);
                send(clifd_rev, p, strlen(p), 0);
                free(p);

                printf("准备启动文件传输线程...\n");
                int abc=pthread_create(&updata, NULL, update_thread, NULL);
                sleep(1);//不加进不去线程
            }
            
        }
        cJSON_Delete(tree);
        cJSON_Delete(root);
    }

    close(sockfd);
    close(tcpfd);
    return 0;
}
